package com.path.planning.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject;
import com.deductive.common.exception.ServiceException;
import com.path.planning.entity.path.*;
import com.path.planning.mapper.path.NavigationMapper;
import com.path.planning.util.path.GeoUtils;
import com.path.planning.util.path.NavigationUtils;
import com.path.planning.util.path.entity.NavigationConstants;
import org.locationtech.jts.geom.LineString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Service
public class NavigationService {
    private static final Logger LOGGER = LoggerFactory.getLogger(NavigationService.class);

    @Resource
    private NavigationMapper navigationMapper;

    /**
     * 并发执行三种公路路径规划策略
     * @param navigationQo 规划条件参数
     * @param resMap 结果存储Map
     * @param countDownLatch 并发计数器
     */
    private void submitRoadNavigationTasks(NavigationQo navigationQo, Map<NavigationConstants.NavigationTypeEnum, NavigationResVo> resMap, CountDownLatch countDownLatch) {
        submitSingleRoadTask(navigationQo, resMap, countDownLatch, NavigationConstants.NavigationTypeEnum.ROAD_LENGTH, "距离优先规划异常");
        submitSingleRoadTask(navigationQo, resMap, countDownLatch, NavigationConstants.NavigationTypeEnum.ROAD_TIME, "时间优先规划异常");
        submitSingleRoadTask(navigationQo, resMap, countDownLatch, NavigationConstants.NavigationTypeEnum.ROAD_HIGHWAY, "高速优先规划异常");
    }

    /**
     * 提交单个公路路径规划任务
     */
    private void submitSingleRoadTask(NavigationQo navigationQo, Map<NavigationConstants.NavigationTypeEnum, NavigationResVo> resMap, CountDownLatch countDownLatch, NavigationConstants.NavigationTypeEnum type, String logMsg) {
        NavigationUtils.NAVIGATION_EXECUTOR.submit(() -> {
            try {
                NavigationResVo navigationResVo = NavigationUtils.doNavigation(navigationQo, type);
                resMap.put(type, navigationResVo);
            } catch (Exception e) {
                LOGGER.error(logMsg, e);
            } finally {
                countDownLatch.countDown();
            }
        });
    }

    /**
     * 查询起、终点最近拓扑点并设置到Qo
     */
    private void setSourceAndTargetTopologyPoint(NavigationQo navigationQo, NavigationConstants.PathTypeEnum pathType) {
        PointDto sourceTopologyPoint = getTopologyPointInfo(navigationQo.getSourceLon(), navigationQo.getSourceLat(), pathType);
        PointDto targetTopologyPoint = getTopologyPointInfo(navigationQo.getTargetLon(), navigationQo.getTargetLat(), pathType);
        navigationQo.setSourceId(sourceTopologyPoint.getId());
        navigationQo.setTargetId(targetTopologyPoint.getId());
    }

    /**
     * 公路路网-路径规划
     * @param navigationQo 规划条件参数
     * @return 规划结果
     */
    public Map<NavigationConstants.NavigationTypeEnum, NavigationResVo> roadNavigation(NavigationQo navigationQo) {
        CountDownLatch countDownLatch = new CountDownLatch(3);
        Map<NavigationConstants.NavigationTypeEnum, NavigationResVo> resMap = new ConcurrentHashMap<>();
        List<PointDto> stopPoints = navigationQo.getStopPoints();
        // 带途径点规划
        if (CollUtil.isNotEmpty(stopPoints)) {
            List<PointDto> stopTopologyPoints = this.buildRoadStopTopologyPoints(navigationQo);
            NavigationUtils.NAVIGATION_EXECUTOR.submit(() -> { // 距离优先
                try {
                    resMap.put(NavigationConstants.NavigationTypeEnum.ROAD_LENGTH,
                            this.singleRoadNavigationWithStops(navigationQo, stopTopologyPoints, NavigationConstants.NavigationTypeEnum.ROAD_LENGTH));
                } catch (Exception e) {
                    LOGGER.error("距离优先查询异常！", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            NavigationUtils.NAVIGATION_EXECUTOR.submit(() -> { // 时间优先
                try {
                    resMap.put(NavigationConstants.NavigationTypeEnum.ROAD_TIME,
                            this.singleRoadNavigationWithStops(navigationQo, stopTopologyPoints, NavigationConstants.NavigationTypeEnum.ROAD_TIME));
                } catch (Exception e) {
                    LOGGER.error("时间优先查询异常！", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            NavigationUtils.NAVIGATION_EXECUTOR.submit(() -> { // 高速优先
                try {
                    resMap.put(NavigationConstants.NavigationTypeEnum.ROAD_HIGHWAY,
                            this.singleRoadNavigationWithStops(navigationQo, stopTopologyPoints, NavigationConstants.NavigationTypeEnum.ROAD_HIGHWAY));
                } catch (Exception e) {
                    LOGGER.error("高速优先查询异常！", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        } else {
            // 无途径点规划，查询起终点最近拓扑点
            setSourceAndTargetTopologyPoint(navigationQo, NavigationConstants.PathTypeEnum.ROAD);
            submitRoadNavigationTasks(navigationQo, resMap, countDownLatch);
        }
        try {
            countDownLatch.await(60, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            LOGGER.error("多线程规划异常", e);
            throw new ServiceException("内部异常");
        }
        return resMap;
    }

    /**
     * 单个公路路径规划(带经停点)
     * @param navigationQo       条件参数
     * @param stopTopologyPoints 点位对应拓扑信息
     * @param navigationType     导航类型
     * @return 规划结果
     */
    private NavigationResVo singleRoadNavigationWithStops(NavigationQo navigationQo, List<PointDto> stopTopologyPoints, NavigationConstants.NavigationTypeEnum navigationType) {
        int segmentCount = stopTopologyPoints.size() - 1;
        CountDownLatch countDownLatch = new CountDownLatch(segmentCount);
        NavigationResVo[] navigationResArr = new NavigationResVo[segmentCount];
        for (int i = 0; i < segmentCount; i++) {
            final int index = i;
            NavigationUtils.NAVIGATION_EXECUTOR.submit(() -> {
                PointDto curPoint = stopTopologyPoints.get(index);
                PointDto nextPoint = stopTopologyPoints.get(index + 1);
                try {
                    navigationResArr[index] = NavigationUtils.doNavigation(
                            new NavigationQo(curPoint.getId(), nextPoint.getId(), navigationQo.getAvoidScopes(), navigationQo.getHeight(), navigationQo.getWeight(), navigationQo.getWidth(), navigationQo.getDepth()),
                            navigationType);
                } catch (Exception e) {
                    LOGGER.error(String.format("公路规划(经停点)异常，异常分段: [%s,%s]-[%s,%s]", curPoint.getLon(), curPoint.getLat(), nextPoint.getLon(), nextPoint.getLat()), e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        try {
            countDownLatch.await(60, TimeUnit.SECONDS);
        } catch (Exception e) {
            LOGGER.error("多线程规划异常！", e);
            throw new ServiceException("内部异常");
        }
        // 汇总所有分段结果
        NavigationAbstractInfoVo totalAbstractInfo = new NavigationAbstractInfoVo();
        List<NavigationDetailInfoVo> totalDetailInfoList = new ArrayList<>();
        Map<String, Double> totalTypePathLengthMap = new HashMap<>();
        totalAbstractInfo.setName(navigationType.getName());
        double totalLength = 0.0;
        double totalTime = 0.0;
        List<LineString> lineStrings = new ArrayList<>();
        for (NavigationResVo segmentRes : navigationResArr) {
            if (segmentRes == null) continue; // 防止某段异常
            NavigationAbstractInfoVo abstractInfo = segmentRes.getAbstractInfo();
            totalLength += abstractInfo.getLength();
            totalTime += abstractInfo.getTime();
            // 解析几何信息
            try {
                lineStrings.add((LineString) GeoUtils.buildGeometryFromGeoJsonStr(JSONObject.toJSONString(abstractInfo.getGeomJson())));
            } catch (Exception e) {
                LOGGER.error("几何信息解析异常", e);
            }
            totalDetailInfoList.addAll(segmentRes.getDetailInfoList());
            segmentRes.getTypePathLength().forEach((k, v) -> totalTypePathLengthMap.put(k, totalTypePathLengthMap.getOrDefault(k, 0.0) + v));
        }
        totalAbstractInfo.setLength(totalLength);
        totalAbstractInfo.setTime(totalTime);
        // 合并所有分段的LineString为一条
        try {
            totalAbstractInfo.setGeomJson(JSONObject.parseObject(GeoUtils.getGeoJsonStrFromGeometry(GeoUtils.unionLineString(lineStrings))));
        } catch (Exception e) {
            LOGGER.error("路径合并异常", e);
        }
        return new NavigationResVo(totalAbstractInfo, totalDetailInfoList, totalTypePathLengthMap);
    }

    /**
     * 构建公路规划经停点信息
     *
     * @param navigationQo 规划条件参数
     * @return 有序的经停点对应的拓扑点ID(包含起点, 终点)
     */
    private List<PointDto> buildRoadStopTopologyPoints(NavigationQo navigationQo) {
        List<PointDto> topologyPoints = new ArrayList<>();
        List<PointDto> stopPoints = navigationQo.getStopPoints();
        topologyPoints.add(navigationMapper.selectRoadNearTopologyPoint(navigationQo.getSourceLon(), navigationQo.getSourceLat()));
        for (PointDto stop : stopPoints) {
            topologyPoints.add(navigationMapper.selectRoadNearTopologyPoint(stop.getLon(), stop.getLat()));
        }
        topologyPoints.add(navigationMapper.selectRoadNearTopologyPoint(navigationQo.getTargetLon(), navigationQo.getTargetLat()));
        return topologyPoints;
    }

    /**
     * 获取点位对应拓扑信息
     *
     * @param originLon 原始点位经度
     * @param originLat 原始点位纬度
     * @param pathType  路网类型 {@link NavigationConstants.PathTypeEnum}
     */
    private PointDto getTopologyPointInfo(Double originLon, Double originLat, NavigationConstants.PathTypeEnum pathType) {
        PointDto topologyPoint = null;
        switch (pathType) {
            case ROAD:
                topologyPoint = navigationMapper.selectRoadNearTopologyPoint(originLon, originLat);
                break;
            case RAILWAY:
                topologyPoint = navigationMapper.selectRailwayNearTopologyPoint(originLon, originLat);
                break;
            case WATERWAY:
                topologyPoint = navigationMapper.selectWaterwayNearTopologyPoint(originLon, originLat);
                break;
        }
        // 计算真实点与拓扑点之间距离
        Double distance = navigationMapper.calculateDistance(topologyPoint.getLon(), topologyPoint.getLat(), originLon, originLat);
        if (distance > 5000) {
            throw new ServiceException("路径规划点位5公里附近内无路网拓扑点，无法规划");
        }
        return topologyPoint;
    }

    /**
     * 铁路路网-路径规划
     *
     * @param navigationQo 规划条件参数
     * @return 规划结果
     */
    public NavigationResVo railwayNavigation(NavigationQo navigationQo) {
        // 查询起、终点最近拓扑点
        PointDto sourceTopologyPoint = this.getTopologyPointInfo(navigationQo.getSourceLon(), navigationQo.getSourceLat(), NavigationConstants.PathTypeEnum.ROAD);
        PointDto targetTopologyPoint = this.getTopologyPointInfo(navigationQo.getTargetLon(), navigationQo.getTargetLat(), NavigationConstants.PathTypeEnum.ROAD);
        navigationQo.setSourceId(sourceTopologyPoint.getId());
        navigationQo.setTargetId(targetTopologyPoint.getId());
        return NavigationUtils.doNavigation(navigationQo, NavigationConstants.NavigationTypeEnum.RAILWAY);
    }

    /**
     * 水路路网-路径规划
     *
     * @param navigationQo 规划条件参数
     * @return 规划结果
     */
    public NavigationResVo waterwayNavigation(NavigationQo navigationQo) {
        // 查询起、终点最近拓扑点
        PointDto sourceTopologyPoint = this.getTopologyPointInfo(navigationQo.getSourceLon(), navigationQo.getSourceLat(), NavigationConstants.PathTypeEnum.ROAD);
        PointDto targetTopologyPoint = this.getTopologyPointInfo(navigationQo.getTargetLon(), navigationQo.getTargetLat(), NavigationConstants.PathTypeEnum.ROAD);
        navigationQo.setSourceId(sourceTopologyPoint.getId());
        navigationQo.setTargetId(targetTopologyPoint.getId());
        return NavigationUtils.doNavigation(navigationQo, NavigationConstants.NavigationTypeEnum.WATERWAY);
    }

    /**
     * 公水联运
     * @param navigationQo 规划条件参数
     * @return 规划结果
     */
    public NavigationResVo roadWaterwayMixDesign(NavigationQo navigationQo) {
        // 查询起、终点最近拓扑点
        PointDto sourceTopologyPoint = this.getTopologyPointInfo(navigationQo.getSourceLon(), navigationQo.getSourceLat(), NavigationConstants.PathTypeEnum.ROAD);
        PointDto targetTopologyPoint = this.getTopologyPointInfo(navigationQo.getTargetLon(), navigationQo.getTargetLat(), NavigationConstants.PathTypeEnum.ROAD);
        navigationQo.setSourceId(sourceTopologyPoint.getId());
        navigationQo.setTargetId(targetTopologyPoint.getId());
        return NavigationUtils.doNavigation(navigationQo, NavigationConstants.NavigationTypeEnum.ROAD_WATERWAY);
    }

    /**
     * 公铁联运
     * @param navigationQo 规划条件参数
     * @return 规划结果
     */
    public NavigationResVo roadRailwayMixDesign(NavigationQo navigationQo) {
        // 查询起、终点最近拓扑点
        PointDto sourceTopologyPoint = this.getTopologyPointInfo(navigationQo.getSourceLon(), navigationQo.getSourceLat(), NavigationConstants.PathTypeEnum.ROAD);
        PointDto targetTopologyPoint = this.getTopologyPointInfo(navigationQo.getTargetLon(), navigationQo.getTargetLat(), NavigationConstants.PathTypeEnum.ROAD);
        navigationQo.setSourceId(sourceTopologyPoint.getId());
        navigationQo.setTargetId(targetTopologyPoint.getId());
        return NavigationUtils.doNavigation(navigationQo, NavigationConstants.NavigationTypeEnum.ROAD_RAILWAY);
    }

}
